/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.teradata.olingo.utils;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.teradata.olingo.processor.core.api.GenericODataPage;
import org.apache.olingo.commons.api.data.Entity;
import org.apache.olingo.commons.api.data.EntityCollection;
import org.apache.olingo.commons.api.data.Property;
import org.apache.olingo.commons.api.edm.*;
import org.apache.olingo.commons.api.ex.ODataRuntimeException;
import org.apache.olingo.commons.api.format.ContentType;
import org.apache.olingo.commons.api.http.HttpStatusCode;
import org.apache.olingo.server.api.OData;
import org.apache.olingo.server.api.ODataApplicationException;
import org.apache.olingo.server.api.ServiceMetadata;
import org.apache.olingo.server.api.serializer.ODataSerializer;
import org.apache.olingo.server.api.serializer.SerializerException;
import org.apache.olingo.server.api.uri.*;
import org.apache.olingo.server.api.uri.queryoption.*;

import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.stream.Collectors;

import static com.teradata.olingo.constant.OdataConst.NAMESPACE;

public class OlingoUtil {


    public static EdmEntitySet getEdmEntitySet(UriInfoResource uriInfo) throws ODataApplicationException {

        List<UriResource> resourcePaths = uriInfo.getUriResourceParts();
        // To get the entity set we have to interpret all URI segments
        if (!(resourcePaths.get(0) instanceof UriResourceEntitySet)) {
            // Here we should interpret the whole URI but in this example we do not support navigation so we throw an
            // exception
            throw new ODataApplicationException("Invalid resource type for first segment.", HttpStatusCode.NOT_IMPLEMENTED
                    .getStatusCode(), Locale.ENGLISH);
        }

        UriResourceEntitySet uriResource = (UriResourceEntitySet) resourcePaths.get(0);

        return uriResource.getEntitySet();
    }

    public static Entity findEntity(EdmEntityType edmEntityType, EntityCollection entitySet,
                                    List<UriParameter> keyParams) throws ODataApplicationException {

        List<Entity> entityList = entitySet.getEntities();

        // loop over all entities in order to find that one that matches all keys in request
        // e.g. contacts(ContactID=1, CompanyID=1)
        for (Entity entity : entityList) {
            boolean foundEntity = entityMatchesAllKeys(edmEntityType, entity, keyParams);
            if (foundEntity) {
                return entity;
            }
        }

        return null;
    }

    public static boolean entityMatchesAllKeys(EdmEntityType edmEntityType, Entity entity, List<UriParameter> keyParams)
            throws ODataApplicationException {

        // loop over all keys
        for (final UriParameter key : keyParams) {
            // key
            String keyName = key.getName();
            String keyText = key.getText();

            // Edm: we need this info for the comparison below
            EdmProperty edmKeyProperty = (EdmProperty) edmEntityType.getProperty(keyName);
            Boolean isNullable = edmKeyProperty.isNullable();
            Integer maxLength = edmKeyProperty.getMaxLength();
            Integer precision = edmKeyProperty.getPrecision();
            Boolean isUnicode = edmKeyProperty.isUnicode();
            Integer scale = edmKeyProperty.getScale();
            // get the EdmType in order to compare
            EdmType edmType = edmKeyProperty.getType();
            EdmPrimitiveType edmPrimitiveType = (EdmPrimitiveType) edmType;

            // Runtime data: the value of the current entity
            // don't need to check for null, this is done in olingo library
            Object valueObject = entity.getProperty(keyName).getValue();

            // now need to compare the valueObject with the keyText String
            // this is done using the type.valueToString //
            String valueAsString = null;
            try {
                valueAsString = edmPrimitiveType.valueToString(valueObject, isNullable, maxLength, precision, scale, isUnicode);
            } catch (EdmPrimitiveTypeException e) {
                throw new ODataApplicationException("Failed to retrieve String value", HttpStatusCode.INTERNAL_SERVER_ERROR
                        .getStatusCode(), Locale.ENGLISH, e);
            }

            if (valueAsString == null) {
                return false;
            }

            boolean matches = valueAsString.equals(keyText);
            if (!matches) {
                // if any of the key properties is not found in the entity, we don't need to search further
                return false;
            }
        }

        return true;
    }

    public static String mkString(String primitiveStr, String bracketsStr) {
        return mkString(primitiveStr, bracketsStr, bracketsStr);
    }

    public static String mkString(String primitiveStr, String startWithStr, String endWithStr) {
        return startWithStr + primitiveStr + endWithStr;
    }

    public static String mkString(Collection<String> collection, String bracketsStr, String splitFlag) {
        return mkString(collection, bracketsStr, bracketsStr, splitFlag);
    }

    public static String mkString(Collection<String> collection, String startWithStr, String endWithStr, String splitFlag) {
        if (splitFlag == null) splitFlag = "";
        StringBuilder targetBuilder = new StringBuilder();
        int len = collection.size();
        for (String s : collection) {
            if (len == 1) {
                targetBuilder.append(startWithStr).append(s).append(endWithStr);
            } else {
                targetBuilder.append(startWithStr).append(s).append(endWithStr).append(splitFlag);
            }

            len--;
        }
        return targetBuilder.toString();
    }

    /**
     * 把字符串按splitStr分割为List，然后将每个元素用aroundStr围绕，最后用linkStr连接为一个字符串
     * elem.split(",").map(e => e.mkString("'", "", "'")).mkString("", ",", "")
     * "A,B,C" => 'A','B','C'
     *
     * @param origStr   = "A,B,C"
     * @param splitStr  = ","  按splitStr分割字符串
     * @param aroundStr = "'" 将分割后的每个元素用该字符围绕
     * @param linkStr   = "," 用linkStr连接元素为一个字符串
     * @return = 'A','B','C'
     */
    public static String splitAroundLink(String origStr, String splitStr, String aroundStr, String linkStr) {
        String result = "";
        List<String> results = new ArrayList<String>();
        Splitter.on(splitStr).split(origStr).forEach(e -> results.add(mkString(e, aroundStr)));
        result = Joiner.on(linkStr).join(results);
        return result;
    }


    /**
     * 把字符串按splitStr分割为List，然后将每个元素用aroundStr围绕，最后用linkStr连接为一个字符串
     * elem.split(",").map(e => e.mkString("'", "", "'")).mkString("", ",", "")
     * "A,B,C" => 'A','B','C'
     *
     * @param origStr   = "A,B,C"
     * @param splitStr  = ","  按splitStr分割字符串
     * @param startWith = "'" 左边包裹字符
     * @param endWith   = "" 右边包裹字符
     * @param linkStr   = "," 用linkStr连接元素为一个字符串
     * @return = 'A','B','C'
     */
    public static String splitAroundLink(String origStr, String splitStr, String startWith, String endWith, String linkStr) {
        String result = "";
        List<String> results = new ArrayList<String>();
        Splitter.on(splitStr).split(origStr).forEach(e -> results.add(mkString(e, startWith, endWith)));
        result = Joiner.on(linkStr).join(results);
        return result;
    }


    /**
     * 将List[String]中的每个字符串用splitAroundLink对其元素挨个处理，形成新的List
     * List("A,B,C","D,E,F") => List("'A','B','C'","'D','E','F'")
     *
     * @param orgiList = List("A,B,C","D,E,F")
     * @return = List("'A','B','C'","'D','E','F'")
     */
    public static List<String> dealElemBySplitAroundLink(List<String> orgiList, String splitStr, String startWith, String endWith) {
        return orgiList.stream().map(e -> {
            return splitAroundLink(e, splitStr, startWith, endWith);
        }).collect(Collectors.toList());
    }


    /**
     * 将List[String]中每个元素都合并起来成为一个String，中间用splitStr拼接，最终在放到List[String]中，其中只有一个元素
     *
     * @param orgiList = List("'A'","'B'","'C'")
     * @param splitStr = "|"
     * @return List("'A'|'B'|'C'")
     */
    public static List<String> dealElemMosaicOnlyOne(List<String> orgiList, String splitStr) {
        String join = Joiner.on(splitStr).join(orgiList);
        return Collections.singletonList(join);
    }

    /*def dealElemMosaicOnlyOne(orgiList: List[String],splitStr: String): List[String] = {
        List(orgiList.mkString(splitStr))
    }*/

    /**
     * Replace the origStr contains replaceMap characters contained in the Key content of the Value
     * 把origStr中的Key字符串替换成Value字符串
     *
     * @param origStr    要被替换的字符串
     * @param replaceMap 替换关系Map，把Key替换为Value
     * @return 替换结果
     */
    public static String replaceByKey(String origStr, Map<String, String> replaceMap, String defaultStr) {
        String targetStr = origStr;
        for (String keyStr : replaceMap.keySet()) {
            targetStr = targetStr.replaceAll(keyStr, replaceMap.getOrDefault(keyStr, defaultStr));
        }
        return targetStr;
    }

    /**
     * 把origStr字符串中包含了replacedList中的元素字符串，统一替换为replaceValue
     *
     * @param origStr      要被替换的原始字符串
     * @param replacedList 要被替换的元素
     * @param replaceValue 要被统一替换的值
     * @return 替换结果
     */
    public static String replaceDefault(String origStr, List<String> replacedList, String replaceValue) {
        String targetStr = origStr;
        for (String keyStr : replacedList) {
            targetStr = targetStr.replaceAll(keyStr, replaceValue);
        }
        return targetStr;
    }




    public static void serializer(OData odata, ContentType responseFormat) {
        try {
            ODataSerializer serializer = odata.createSerializer(responseFormat);

        } catch (SerializerException e) {
            e.printStackTrace();
        }
    }

    /**
     * UriParameter is a key, so it is in CrudRepository as Serializable.
     * TODO: for now just string or long
     */
    public static Serializable getUriParameterValue(UriParameter param) {
        Serializable result = param.getText();

        String temp = result.toString();
        if (temp.startsWith("'")) {
            result = temp.substring(1, temp.length() - 1);
        } else {
            result = Long.valueOf(temp);
        }

        return result;
    }


    /**
     * @param uri
     * @return
     * @throws SerializerException
     */
    public static List<UriResource> getSelectList(UriInfo uri) throws SerializerException {
        List<UriResource> selectItemList = new ArrayList<UriResource>();
        SelectOption selectOption = uri.getSelectOption();
        for (SelectItem selectItem : selectOption.getSelectItems()) {
            selectItemList.add(selectItem.getResourcePath().getUriResourceParts().get(0));
        }
        return selectItemList;
    }

    /**
     * 获取 selectList
     *
     * @param uri
     * @param entity
     * @return
     * @throws SerializerException
     */
    public static String getSelectClause(OData odata, UriInfo uri, EdmEntityType entity) throws SerializerException {
        return odata.createUriHelper().buildContextURLSelectList(entity, null, uri.getSelectOption());
    }

    /**
     * 从服务元数据中，获取实体集字段元数据信息
     *
     * @param serviceMetadata
     * @param edmEntitySet
     * @return
     */
    public static List<EdmProperty> getStructuralProperty(ServiceMetadata serviceMetadata, EdmEntitySet edmEntitySet) {
        List<EdmProperty> structuralPropertyList = new ArrayList<EdmProperty>();
        Edm metadataEdm = serviceMetadata.getEdm();
        EdmEntityType entityType = metadataEdm.getEntityType(new FullQualifiedName(NAMESPACE + "." + edmEntitySet.getName()));
        for (String propName : entityType.getPropertyNames()) {
            EdmProperty structuralProperty = entityType.getStructuralProperty(propName);
            structuralPropertyList.add(structuralProperty);
        }
        return structuralPropertyList;
    }

    /**
     * 从服务元数据中，获取实体集中
     *
     * @param serviceMetadata
     * @param edmEntitySet
     * @return
     */
    public static List<EdmNavigationProperty> getNavigationProperty(ServiceMetadata serviceMetadata, EdmEntitySet edmEntitySet) {
        List<EdmNavigationProperty> navigationPropertyList = new ArrayList<EdmNavigationProperty>();
        Edm metadataEdm = serviceMetadata.getEdm();
        EdmEntityType entityType = metadataEdm.getEntityType(new FullQualifiedName(NAMESPACE + "." + edmEntitySet.getName()));
        for (String propName : entityType.getNavigationPropertyNames()) {
            EdmNavigationProperty navigationProperty = entityType.getNavigationProperty(propName);
            navigationPropertyList.add(navigationProperty);
        }
        return navigationPropertyList;
    }

    /**
     * 获取 Page 对象
     *
     * @param uri
     * @return
     */
    public static GenericODataPage getPage(UriInfo uri) {
        int skipNumber = 0;
        int topNumber = 0;
        String skipToken = "";


        // handle $skip
        SkipOption skipOption = uri.getSkipOption();
        if (skipOption != null) {
            skipNumber = skipOption.getValue();
        }

        // handle $top
        TopOption topOption = uri.getTopOption();
        if (topOption != null) {
            topNumber = topOption.getValue();
        }

        SkipTokenOption skipTokenOption = uri.getSkipTokenOption();
        if (skipTokenOption != null) {
            skipToken = skipTokenOption.getValue();
        }

        return new GenericODataPage(uri, skipNumber, topNumber, skipToken);
    }

    /**
     * 获取是否需要计算count
     *
     * @param uri
     * @return
     */
    public static Boolean getIsCount(UriInfo uri) {
        Boolean isCount = false;
        // handle $count: always return the original number of entities, without considering $top and $skip
        CountOption countOption = uri.getCountOption();
        if (countOption != null) {
            isCount = countOption.getValue();
        }
        return isCount;
    }

    /**
     * 获取orderby参数集
     *
     * @return
     */
    public static List<OrderByItem> getOrderItemList(UriInfo uri) {
        //http://localhost:8081/chinabond.svc/members?$select=first_name,last_name&$orderby=first_name%20asc,last_name%20desc
        OrderByOption orderByOption = uri.getOrderByOption();
        List<OrderByItem> orderItemList = new ArrayList<OrderByItem>();
        if (orderByOption != null) {
            orderItemList = orderByOption.getOrders();
            /*for (OrderByItem orderByItem : orderItemList) {
                System.out.println("order:" + orderByItem.getExpression());
                System.out.println("isDesc:" + orderByItem.isDescending());
            }*/
        }
        return orderItemList;
    }

    public static void createId(String entitySetName, Entity entity, List<EdmKeyPropertyRef> keyPropertyRefs) {
        try {
            StringBuilder idKey = new StringBuilder();
            idKey.append(entitySetName);
            idKey.append("(");
            String comma = ",";
            int i = keyPropertyRefs.size();
            for (EdmKeyPropertyRef keyPropertyRef : keyPropertyRefs) {
                comma = (i == 1) ? "" : ",";
                idKey.append(keyPropertyRef.getName());
                idKey.append("=");

                String keyName = keyPropertyRef.getName();

                Property property = entity.getProperty(keyName);

                Object value;
                if (property == null) {
                    value = "null";
                } else {
                    value = property.getValue();
                }

                if (keyPropertyRef.getProperty().getType().getFullQualifiedName().equals(EdmPrimitiveTypeKind.String.getFullQualifiedName())){
                    value = mkString(value.toString(), "'");
                }

                idKey.append(value);
                idKey.append(comma);
                i --;
            }
            idKey.append(")");
            //String keyStr = Joiner.on(",").join(propStrList);
            //entity.setId(new URI(keyStr));
            entity.setId(new URI(idKey.toString()));
        } catch (URISyntaxException e) {
            throw new ODataRuntimeException("Unable to create id for entity: " + entitySetName, e);
        }
    }

}
